import uuid
from typing import Tuple, Union, Callable, Coroutine, Any

from fastapi import Response, Request
from fastapi.routing import APIRoute


class CustomerBaseRoute(APIRoute):

    def __init__(self, path: str, endpoint: Callable[..., Any], **kwargs):
        super().__init__(path, endpoint, **kwargs)

    async def do_before(self, request, *args, **kwargs) -> Tuple[bool, Union[Response, None]]:

        return True, None

    async def do_after(self, request, response) -> Response:
        return response

    def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
        original_route_handler = super(CustomerBaseRoute, self).get_route_handler()
        async def _wrapper(request: Request) -> Response:
            res = await self.do_before(request)
            ok, resp_or_none = await self.do_before(request)
            if not ok:
                return resp_or_none
            response: Response = await original_route_handler(request)
            resp = await self.do_after(request, response)
            return resp
        return _wrapper


class ZipCustomerRoute:

    def __new__(cls, *route_cls_list):
        c = type(
            f'{uuid.UUID.hex}',
            (CustomerBaseRoute, ),
            {
                "route_cls_list": route_cls_list
            }
        )
        c.__init__ = __init__
        c.do_before = do_before
        c.do_after = do_after
        return c


def __init__(self, path: str, endpoint: Callable[..., Any], **kwargs):
    CustomerBaseRoute.__init__(self, path, endpoint, **kwargs)
    self.do_before_list = []
    self.do_after_list = []
    for route_cls in self.route_cls_list:
        o = route_cls(path, endpoint)
        self.do_before_list.append(o.do_before)
        self.do_after_list.append(o.do_after)
    self.do_after_list.reverse()


async def do_before(self, request) -> Tuple[bool, Union[Response, None]]:
    for f in self.do_before_list:
        ok, resp_or_none = await f(request)
        if not ok:
            return ok, resp_or_none
        else:
            continue
    return True, None


async def do_after(self, request, response) -> Response:
    for f in self.do_after_list:
        response = await f(request, response)
    return response





